/** 
    Copyright 2010 Henrik Andersson
    
    This file is part of Loopist.

    Loopist is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Loopist is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Loopist.  If not, see <http://www.gnu.org/licenses/>.
*/


package se.dinamic.loopist;

import java.nio.FloatBuffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;

import android.util.Log;
import android.content.Context;
import android.view.View;
import android.view.MotionEvent;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;

import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import android.opengl.GLSurfaceView;
import javax.microedition.khronos.opengles.GL10;

public class LoopistView extends GLSurfaceView implements LoopistEngine.Listener, GLSurfaceView.Renderer 
{
	
	/** Bar count of track. */
	private int _barCount;
	/** Count of beats per bar in track */
	private int _beatsPerBar;
	/** Count of tracks to display */
	private int _trackCount;
	/** Pixel height of each track. */
	private int _trackPixelHeight;
	/** Pixel width of each track. */
	private int _trackPixelWidth;
	/** Screen width */
	private float _screenWidth;
	/** Screen height */
	private float _screenHeight;

	/** The motion detector class for touch display and actions.. */
	MotionDetector _motionDetector;
	
	/** Playback position 0-1 of whole track.. */
	private float _currentPlaybackPosistion;
	/** LoopistEngine reference. \see LoopistEngine */
	private LoopistEngine _loopistEngine;
	
	/** Vertex  array for playpack posistion marker object */
	private FloatBuffer _vaPlaybackPosistionMark;
	/** Color array for playpack posistion marker object */
	private FloatBuffer _caPlaybackPosistionMark;
	/** Vertext array for beatlines object */
	private FloatBuffer _vaBeatLines;
	/** Color array for beatlines object */
	private FloatBuffer _caBeatLines;
	/** Vertext array for horizontal waveform lines object */
	private FloatBuffer _vaWaveformLines;
	/** Color array for horizontal waveform lines object */
	private FloatBuffer _caWaveformLines;


	private Texture _textureTrackHeaderPlate;
	private Texture _textureTrackFramePlate;
	private Texture _textureTrackEndPlate;
	private Texture _textureTrackRecordLed;
	private Texture _textureTrackRecordLedLit;


	/** Recording LED intensity.
		@see onClock() onBeat() 
	*/
	private float _recordLEDIntensity = 0.0f;
	

	/** Track header width 5% of view. */
	private final static float TRACK_HEADER_WIDTH=0.05f;		
	/** Track header width 1% of view. */
	private final static float TRACK_END_WIDTH=0.0125f;			

	private WaveformTexture[] _waveformTextures;
	
	private static float TEXTURED_PLANE_VERTICES[] = {
		0.0f, 0.0f,  0.0f,	
		1.0f, 0.0f,  0.0f,
		0.0f,  1.0f,  0.0f,
		1.0f,  1.0f,  0.0f
	};
	
	private static float TEXTURED_PLANE_VERTEX_COLORS[] = {
		1.0f,1.0f,1.0f,1.0f,
		1.0f,1.0f,1.0f,1.0f,
		1.0f,1.0f,1.0f,1.0f,
		1.0f,1.0f,1.0f,1.0f
	};
	
	private static float TEXTURED_PLANE_TEXTURE_COORDS[] = {
		0.0f, 1.0f,
		1.0f, 1.0f,
		0.0f, 0.0f,
		1.0f, 0.0f
	};
	
	private static FloatBuffer _vaTexturedPlane = null;
	private static FloatBuffer _tcTexturedPlane = null;
	private static FloatBuffer _vcTexturedPlane = null;
	private static FloatBuffer _vcColoredPlane = null;
	
	/** The beat length in millis. */
	private int _beatLength;
	
	public LoopistView(LoopistEngine loopistEngine,Context context) {
		super(context);
		_loopistEngine=loopistEngine;
		
		_barCount=_loopistEngine.getBars();
		_beatsPerBar=_loopistEngine.getBeatsPerBar();
		_trackCount=_loopistEngine.getTrackCount();
		_beatLength = _loopistEngine.getBeatLength();
		
		// Setup waveformtextures
		_waveformTextures=new WaveformTexture[_trackCount];
		for(int t=0;t<_trackCount;t++)
			_waveformTextures[t] = new WaveformTexture( _loopistEngine.getTrackFrameCount(), _loopistEngine.getTrackColor(t) );
		
		// Generate renderble objects
		createBeatLinesObject();
		createPlaybackPosistionObject();
		createTexturedPlane();
		createWaveformLines();
		
		// Load textures from resources
		 _textureTrackHeaderPlate = new Texture();
		 _textureTrackHeaderPlate.loadFromResources( context, R.drawable.track_header_plate );
		
		_textureTrackEndPlate = new Texture();
		_textureTrackEndPlate.loadFromResources( context, R.drawable.track_end_plate );
		
		_textureTrackFramePlate = new Texture();
		_textureTrackFramePlate.loadFromResources( context, R.drawable.track_frame_plate );
		
		_textureTrackRecordLed = new Texture();
		_textureTrackRecordLed.loadFromResources( context, R.drawable.track_red_led );
		
		_textureTrackRecordLedLit = new Texture();
		_textureTrackRecordLedLit.loadFromResources( context, R.drawable.track_red_led_lit );
		
		_motionDetector = new MotionDetector( loopistEngine );
		
		  // Get key and touch events...
		setFocusable( true ); 
		setFocusableInTouchMode( true );
		setOnTouchListener( _motionDetector );
		
		//setDebugFlags(DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS);
		
		// Create and start clock thread 
		ClockThread job=new ClockThread( this );
		job.start();
		
		// Setting the GL renderer
		setRenderer( this );
	}
	
	
	/*
	 * LoopistEngine.Listener implementation...
	*/
	public void onBeat() { _recordLEDIntensity=1.0f; }
	public void onBar() { Log.v("Loopist","On bar..."); }
	
	
	public void onPlaybackPosition(float position) {
		_currentPlaybackPosistion = position;
		postInvalidate();
	}
	
	private static FloatBuffer createNativeOrderFloatBuffer(int size) {
		return ByteBuffer.allocateDirect(size<<2).order(ByteOrder.nativeOrder()).asFloatBuffer();
	}
	
	private static class MotionDetector implements OnTouchListener {
		/** Screen X coordinate for start of action. */
		private float _actionStartX;
		/** Screen Y coordinate for start of action. */
		private float _actionStartY;
		/** Screen width */
		private float _screenWidth;
		/** Screen height */
		private float _screenHeight;
		/** Pixel height of each track. */
		private int _trackPixelHeight=0;
		
		/** The active track used for actions trigged by touch movements.. */
		private int _activeTrack;
		private boolean _isFirstTime = true;
		private LoopistEngine _loopistEngine;
		
		public MotionDetector(LoopistEngine loopist) {
			_loopistEngine=loopist;
		}
		
		public void setScreenHeight(float height) { _screenHeight = height; }
		public void setScreenWidth(float width) { _screenWidth = width; }
		public void setTrackPixelHeight(int pixels) { _trackPixelHeight = pixels; }
		
		public int getActiveTrack() { return _activeTrack; }
		
		/*
		 * View.OnTouchListener implementation
		 */
		private float _sourceVolume=0.0f;
		public boolean onTouch(View view, MotionEvent event) {
			boolean processed=false;
			switch( event.getAction() ) {
				case MotionEvent.ACTION_DOWN:
				{
					Log.v("Loopist","onTouch(), Action down @ "+event.getX()+","+event.getY());
					
					_activeTrack = (int)( ( (_screenHeight-event.getY()) / _trackPixelHeight ) );
					_actionStartX = event.getX();
					_actionStartY = event.getY();
					_sourceVolume=_loopistEngine.getTrackVolume(_activeTrack);
					Log.v("Loopist","Motion begin, track under finger " + _activeTrack);
					processed=true;
				} break;
				
				case MotionEvent.ACTION_CANCEL:
				{
					Log.v("Loopist","Motion cancelled.");
					processed=true;
				} break;
				
				case MotionEvent.ACTION_MOVE:
				{
					float cx = event.getX();
					float cy = event.getY();
					Log.v("Loopist","onTouch(), Action move @ " +cx +","+cy+" edgeflags "+event.getEdgeFlags() );
					
					float ythreshold=_trackPixelHeight*0.25f;
					// Check if movement is left/right within same track as action down..
					if( 
						( cy > _actionStartY-ythreshold && cy < _actionStartY+ythreshold )
					) {
						float delta = (cx-_actionStartX) * (1.0f/( _screenWidth * 0.50f));
						Log.v("Loopist","onTouch() Volume change "+delta);
						_loopistEngine.setTrackVolume( _activeTrack,_sourceVolume+delta);
						processed=true;	
					} else {
						event.setAction(MotionEvent.ACTION_CANCEL);
					}
					
				} break;
				
				case MotionEvent.ACTION_UP:
				{
					if( _isFirstTime )  {
						_loopistEngine.play();
						_isFirstTime=false;
					} else {
						float cx = event.getX();
						float cy = event.getY();
						Log.v("Loopist","onTouch(), Action up @ " +cx +","+cy+" edgeflags "+event.getEdgeFlags() );
						float ythreshold=_trackPixelHeight*0.50f;
						// Let's see if we got a click touch..
						if( ( Math.abs( _actionStartX-cx ) < 5 ) && ( Math.abs( _actionStartY-cy ) < 5 ) ) 
						{ // Simple press detected let's que up active track for record...
							_loopistEngine.recordTrack( _activeTrack );
							processed=true;					
						}
						else
							
						// Let's check if up action appears on active tracks, trackheader
						if (  cx < 32 &&  ( cy > _actionStartY-ythreshold && cy < _actionStartY+ythreshold ) ) 
						{
							Log.v("Loopist","Motion dragged left ...");
							_loopistEngine.wipeTrack( _activeTrack );
							// Then turn back volume to orginal..
							_loopistEngine.setTrackVolume( _activeTrack,_sourceVolume);
							processed=true;
						}
					}
					
				} break;
			}
			return processed;
		}
	}
	
	/*
	 * GLSurfaceView implementations...
	 */
	@Override
	public void onSurfaceCreated( GL10 gl, EGLConfig config ) {
		// Let's setup gl

		gl.glDisable( GL10.GL_DITHER );
		gl.glHint( GL10.GL_PERSPECTIVE_CORRECTION_HINT , GL10.GL_FASTEST );
		gl.glClearColor( 0.0f, 0.0f, 0.0f, 0.0f);
		gl.glFrontFace(gl.GL_CCW);
		gl.glEnable(GL10.GL_CULL_FACE);
		gl.glCullFace(GL10.GL_BACK);
		gl.glShadeModel(GL10.GL_FLAT);

		gl.glEnable(GL10.GL_BLEND);
		gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
		gl.glEnable(GL10.GL_COLOR_MATERIAL);
		gl.glEnable(GL10.GL_TEXTURE_2D);

		// All objects rendered have varr, carr and textcoordarr soo this is put here
		// for performance
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
		gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		
		
		// Let finalize textures...
		_textureTrackHeaderPlate.finalize( gl );
		_textureTrackEndPlate.finalize( gl );
		_textureTrackFramePlate.finalize( gl );
		_textureTrackRecordLed.finalize( gl );
		_textureTrackRecordLedLit.finalize( gl );
	
		for(int t=0;t<_trackCount;t++) {
			_waveformTextures[t].finalize( gl );
			_waveformTextures[t].addSamples( _loopistEngine.getTrackBuffer(t) );
			_waveformTextures[t].update(gl);
			_loopistEngine.setTrackListener( t, _waveformTextures[t] );
		}
		
	}
	private float _ratio=0;
	@Override
	public void onSurfaceChanged( GL10 gl, int width, int height ) {
		gl.glViewport(0, 0, width, height);

		_screenWidth = width;
		_screenHeight = height;
		_motionDetector.setScreenHeight( _screenHeight );
		_motionDetector.setScreenWidth( _screenWidth);
		_motionDetector.setTrackPixelHeight((int)(_screenHeight/_trackCount));
		
		// Calculate new projection matrix on viewport dimension change
		_ratio = (float) width / height;
		gl.glMatrixMode(GL10.GL_PROJECTION);
		gl.glLoadIdentity();
		//gl.glFrustumf(-ratio, ratio, -1, 1, 1, 40);
		gl.glOrthof(0, 1, 0, 1 , 0.0f, 10.0f);
	}
	
	@Override
	public void onDrawFrame( GL10 gl ) {
		// Clear gl buffer
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT |  GL10.GL_STENCIL_BUFFER_BIT);

		// Setup viewpoint and camera
		gl.glMatrixMode(GL10.GL_MODELVIEW);
		gl.glLoadIdentity();

		// Render the loopist tracks etc...
		renderBeatLines( gl );
		
		renderTrackWaveform( gl );
		
		renderPlaybackPosition( gl );
		
		renderTrackFrames( gl );
		
		// Calculations...
		for(int t=0;t<_trackCount;t++) 
			_waveformTextures[t].update(gl);
	}
	
	public void onClock(long delta) {
		// Let's calculate new intensity for recording LED if any track is in pending record state
		if( _loopistEngine.isPendingRecord()  && _recordLEDIntensity > 0.0f ) {
			_recordLEDIntensity = Math.max( 0.0f, _recordLEDIntensity-( delta/(_beatLength/3.0f) ) );
		}
	}
	
	/** Render bars and beat lines. */
	private void renderBeatLines(GL10 gl) {
		gl.glPushMatrix();
		gl.glDisable(GL10.GL_TEXTURE_2D);
		gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		
		gl.glVertexPointer(3, gl.GL_FLOAT, 0, _vaBeatLines);
		gl.glColorPointer(4, gl.GL_FLOAT, 0, _caBeatLines);
		gl.glDrawArrays(gl.GL_LINES, 0, _vaBeatLines.capacity()/3 );
		
		gl.glPopMatrix();
	}
	
	/** Render colored track waveform baseline. */
	private void renderTrackWaveformLines(GL10 gl) {
		gl.glPushMatrix();
		gl.glDisable(GL10.GL_TEXTURE_2D);
		gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		
		gl.glVertexPointer(3, gl.GL_FLOAT, 0, _vaWaveformLines);
		gl.glColorPointer(4, gl.GL_FLOAT, 0, _caWaveformLines);
		gl.glDrawArrays(gl.GL_LINES, 0, _vaWaveformLines.capacity()/3 );
		
		gl.glPopMatrix();
	}
	
	/** Render the playback position marker. */
	private void renderPlaybackPosition(GL10 gl) {
		gl.glPushMatrix();
		gl.glTranslatef( TRACK_HEADER_WIDTH + ((1.0f-TRACK_HEADER_WIDTH-TRACK_END_WIDTH)*_currentPlaybackPosistion),0,0);
		gl.glDisable(GL10.GL_TEXTURE_2D);
		gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		
		gl.glVertexPointer(3, gl.GL_FLOAT, 0, _vaPlaybackPosistionMark);
		gl.glColorPointer(4, gl.GL_FLOAT, 0, _caPlaybackPosistionMark);
		gl.glDrawArrays(gl.GL_LINES, 0, _vaPlaybackPosistionMark.capacity()/3 );
		
		gl.glPopMatrix();
	}
	
	/***/
	private void renderTrackWaveform(GL10 gl) {
		gl.glPushMatrix();
		
		// First off render colored track waveform baselines
		renderTrackWaveformLines(gl);
		
		// For each track render waveform texture...
		float trackHeight=(1.0f/_trackCount);
		gl.glTranslatef(TRACK_HEADER_WIDTH,0,0);
		gl.glScalef(1.0f-TRACK_HEADER_WIDTH-TRACK_END_WIDTH, trackHeight,1.0f);
		
		for( int t=0;t<_trackCount;t++) {
			_waveformTextures[t].render( gl );
			gl.glTranslatef(0,1.0f,0);
			
		}
		
		gl.glPopMatrix();
	}
	
	/** Render the track header,frame and end plate... 
		@todo needs optimization... to much matrix pushing...
	*/
	private void renderTrackFrames(GL10 gl) {
		gl.glPushMatrix();
		float trackHeight=(1.0f/_trackCount);
		gl.glScalef(1.0f, trackHeight,1.0f);	
			
				
		// For each track
		for( int t=0;t<_trackCount;t++) {
				
			// Frame plate
			gl.glPushMatrix();
				gl.glTranslatef(0,1.0f*t,0);
				renderTexturedPlane( gl, _textureTrackFramePlate, 1.0f );
			gl.glPopMatrix();	
			
			// Header plate
			gl.glPushMatrix();
				gl.glScalef(TRACK_HEADER_WIDTH, 1.0f,1.0f);
				gl.glTranslatef(0,1.0f*t,0);
				gl.glPushMatrix();
					gl.glScalef(1,_loopistEngine.getTrackVolume( t ) ,1);
					renderColoredPlane( gl, 0.4f,0.7f,0.4f,1.0f);
				gl.glPopMatrix();
				renderTexturedPlane( gl, _textureTrackHeaderPlate, 1.0f );
			
				// Render track recording led...
				gl.glScalef(0.6f,0.125f,1.0f);
				gl.glTranslatef(0.32f,6.55f,0);
				renderTexturedPlane( gl, _textureTrackRecordLed, 1.0f );
				if( _loopistEngine.isPendingRecord() && _loopistEngine.getRecordingTrack()==t ) 
					renderTexturedPlane( gl, _textureTrackRecordLedLit, _recordLEDIntensity );
				else if( _loopistEngine.isRecording() && _loopistEngine.getRecordingTrack()==t)
					renderTexturedPlane( gl, _textureTrackRecordLedLit, 1.0f);
					
			gl.glPopMatrix();	
			
			// End plate
			gl.glPushMatrix();
				gl.glTranslatef( (1.0f-TRACK_END_WIDTH),0,0);
				gl.glScalef(TRACK_END_WIDTH, 1.0f,1.0f);
				gl.glTranslatef(0,1.0f*t,0);
				renderTexturedPlane( gl, _textureTrackEndPlate, 1.0f );
			gl.glPopMatrix();	

		}
		
		gl.glPopMatrix();
	}
	
	private void createWaveformLines() {
		
		float trackHeight=1.0f/_trackCount;
		
		_vaWaveformLines = createNativeOrderFloatBuffer( (_trackCount*2)*3 );
		_caWaveformLines = createNativeOrderFloatBuffer( (_trackCount*2)*4 );
		
		for(int t=0;t<_trackCount;t++) {
			_vaWaveformLines.put(TRACK_HEADER_WIDTH); _vaWaveformLines.put((trackHeight*t)+(trackHeight/2.0f)); _vaWaveformLines.put(0.0f);
			_vaWaveformLines.put(1.0f); _vaWaveformLines.put((trackHeight*t)+(trackHeight/2.0f)); _vaWaveformLines.put(0.0f);
			
			int []cols=_loopistEngine.getTrackColor(t);
			
			_caWaveformLines.put(cols[0]/255.0f);_caWaveformLines.put(cols[1]/255.0f);_caWaveformLines.put(cols[2]/255.0f);_caWaveformLines.put(0.7f);
			_caWaveformLines.put(cols[0]/255.0f);_caWaveformLines.put(cols[1]/255.0f);_caWaveformLines.put(cols[2]/255.0f);_caWaveformLines.put(0.7f);
		
		}
		_vaWaveformLines.position(0);
		_caWaveformLines.position(0);
	}
	
	private void createPlaybackPosistionObject() {
		// Pre Generate vertext buffers for beat lines
		_vaPlaybackPosistionMark = createNativeOrderFloatBuffer( 2*3 );	// vertices needed for GL_LINES rendering playback position mark
		_caPlaybackPosistionMark = createNativeOrderFloatBuffer( 2*4 );   	// colors for each vertex...
		
		_vaPlaybackPosistionMark.mark();
		_caPlaybackPosistionMark.mark();
		
		_vaPlaybackPosistionMark.put(0.0f); _vaPlaybackPosistionMark.put(0.0f); _vaPlaybackPosistionMark.put(0.0f);
		_vaPlaybackPosistionMark.put(0.0f); _vaPlaybackPosistionMark.put(1.0f); _vaPlaybackPosistionMark.put(0.0f);
		
		_caPlaybackPosistionMark.put(1.0f);_caPlaybackPosistionMark.put(1.0f);_caPlaybackPosistionMark.put(1.0f);_caPlaybackPosistionMark.put(0.8f);
		_caPlaybackPosistionMark.put(1.0f);_caPlaybackPosistionMark.put(1.0f);_caPlaybackPosistionMark.put(1.0f);_caPlaybackPosistionMark.put(0.8f);
		
		_vaPlaybackPosistionMark.reset();
		_caPlaybackPosistionMark.reset();
		
	}
	
	private void createBeatLinesObject() {
		// Pre Generate vertext buffers for beat lines
		int bc=(_barCount*_beatsPerBar);
		_vaBeatLines = createNativeOrderFloatBuffer( (bc*2)*3 );	// vertices needed for GL_LINES rendering of bar and beat lines
		_caBeatLines = createNativeOrderFloatBuffer( (bc*2)*4 );   // colors for each vertex...
		
		_vaBeatLines.mark();
		_caBeatLines.mark();
		float bw = (1.0f-TRACK_HEADER_WIDTH-TRACK_END_WIDTH) /bc;
		float cx=TRACK_HEADER_WIDTH;
		for( int c=0 ; c<bc ; c++) {
			_vaBeatLines.put(cx); _vaBeatLines.put(0.0f); _vaBeatLines.put(0.0f);
			_vaBeatLines.put(cx); _vaBeatLines.put(1.0f); _vaBeatLines.put(0.0f);
			cx+=bw;
			if( c%4 == 0 ) {
				_caBeatLines.put(1.0f);_caBeatLines.put(1.0f);_caBeatLines.put(1.0f);_caBeatLines.put(0.5f);
				_caBeatLines.put(1.0f);_caBeatLines.put(1.0f);_caBeatLines.put(1.0f);_caBeatLines.put(0.5f);
			} else {
				_caBeatLines.put(1.0f);_caBeatLines.put(1.0f);_caBeatLines.put(1.0f);_caBeatLines.put(0.3f);
				_caBeatLines.put(1.0f);_caBeatLines.put(1.0f);_caBeatLines.put(1.0f);_caBeatLines.put(0.3f);
			}
		}
		_vaBeatLines.reset();
		_caBeatLines.reset();
	}
	
	
	private void createTexturedPlane() {
		_tcTexturedPlane = createNativeOrderFloatBuffer( TEXTURED_PLANE_TEXTURE_COORDS.length );
		_tcTexturedPlane.put( TEXTURED_PLANE_TEXTURE_COORDS );
		_tcTexturedPlane.position(0);
		
		_vaTexturedPlane = createNativeOrderFloatBuffer( TEXTURED_PLANE_VERTICES.length );
		_vaTexturedPlane.put( TEXTURED_PLANE_VERTICES );
		_vaTexturedPlane.position(0);
		
		_vcTexturedPlane = createNativeOrderFloatBuffer( TEXTURED_PLANE_VERTEX_COLORS.length );
		_vcTexturedPlane.put( TEXTURED_PLANE_VERTEX_COLORS );
		_vcTexturedPlane.position(0);	
		
		_vcColoredPlane = createNativeOrderFloatBuffer( TEXTURED_PLANE_VERTEX_COLORS.length );
		_vcColoredPlane.put( TEXTURED_PLANE_VERTEX_COLORS );
		_vcColoredPlane.position(0);
	}
	
	/** Renders a colored plane. */
	private void renderColoredPlane( GL10 gl, float red, float green, float blue, float alpha ) {
		gl.glPushMatrix();
		gl.glDisable(GL10.GL_TEXTURE_2D);
		_vcColoredPlane.position(0);
		_vcColoredPlane.put(red); _vcColoredPlane.put(green); _vcColoredPlane.put(blue); _vcColoredPlane.put(alpha);
		_vcColoredPlane.put(red); _vcColoredPlane.put(green); _vcColoredPlane.put(blue); _vcColoredPlane.put(alpha);
		_vcColoredPlane.put(red); _vcColoredPlane.put(green); _vcColoredPlane.put(blue); _vcColoredPlane.put(alpha);
		_vcColoredPlane.put(red); _vcColoredPlane.put(green); _vcColoredPlane.put(blue); _vcColoredPlane.put(alpha);
		_vcColoredPlane.position(0);
		
		// Render tile textured plane..
		gl.glVertexPointer(3, gl.GL_FLOAT, 0, _vaTexturedPlane );
		gl.glColorPointer(4, gl.GL_FLOAT, 0, _vcColoredPlane );
		gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, _vaTexturedPlane.capacity()/3 );
		gl.glPopMatrix();
	
	}
	
	private void renderTexturedPlane( GL10 gl, Texture texture, float alpha) {
		gl.glPushMatrix();
		gl.glEnable(GL10.GL_TEXTURE_2D);
		gl.glBindTexture(GL10.GL_TEXTURE_2D, texture.id[0]);
		gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		
		// Set alpha 
		_vcTexturedPlane.put(3,alpha); _vcTexturedPlane.put(7,alpha); _vcTexturedPlane.put(11,alpha); _vcTexturedPlane.put(15,alpha);
		
		// Render tile textured plane..
		gl.glVertexPointer(3, gl.GL_FLOAT, 0, _vaTexturedPlane );
		gl.glTexCoordPointer(2, gl.GL_FLOAT, 0, _tcTexturedPlane );
		gl.glColorPointer(4, gl.GL_FLOAT, 0, _vcTexturedPlane );
		gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, _vaTexturedPlane.capacity()/3 );
		gl.glPopMatrix();
	}
	
	private static class Texture {
		public ByteBuffer data=null;
		public int width;
		public int height;
		public int id[];
		
		private void loadFromResources( Context context, int id) {
			
			Log.d("Loopist","Texture.loadFromResources() decoding resource.");
			
			// Ensure bitmap is 4 chan 8bit pic
			Bitmap bmp = BitmapFactory.decodeResource( context.getResources(), id );
			Bitmap realbmp = bmp.copy(Config.ARGB_8888, false);
		
			width = realbmp.getWidth();
			height = realbmp.getHeight();
			data = argb2rgba(realbmp);  
			Log.d("Loopist","Texture.loadTextureFromResources() width "+width+" height "+ height +" bytes " +data.capacity() );
			bmp.recycle();
			realbmp.recycle();
			
		}
		
		
		public void finalize(GL10 gl) {
			Log.d("Loopist","Texture.finialize() Finalizing texture... ");
			id = new int[1];
			gl.glGenTextures( 1, id, 0); 
			gl.glBindTexture(GL10.GL_TEXTURE_2D, id[0] );
			gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, GL10.GL_RGBA, width, height, 0, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, data);
			gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
			gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); 
			data=null;
		}
	}
	
	
	public static ByteBuffer argb2rgba(Bitmap bmp)
	{
		ByteBuffer bb = ByteBuffer.allocateDirect(bmp.getHeight() * bmp.getWidth() * 4);
		bb.order(ByteOrder.BIG_ENDIAN);
		IntBuffer ib = bb.asIntBuffer();
		for (int y = bmp.getHeight() - 1; y > -1; y--)
		{

			for (int x = 0; x < bmp.getWidth(); x++)
			{
				int pix = bmp.getPixel(x, bmp.getHeight() - y - 1);
				int alpha = ((pix >> 24) & 0xFF);
				int red = ((pix >> 16) & 0xFF);
				int green = ((pix >> 8) & 0xFF);
				int blue = ((pix) & 0xFF);

				ib.put(red << 24 | green << 16 | blue << 8 | alpha);
			}
		}
		bb.position(0);
		return bb;
	} 

	/** This is clock thread, providing animation calculation ticks.. 
		@todo In future this delta clock might be placed within render loop onDrawSurface()
	*/
	private static class ClockThread extends Thread {
		private long _previous=0;
		LoopistView _loopistView = null;
		
		public ClockThread( LoopistView view ) {
			_loopistView = view;
			_previous = java.lang.System.currentTimeMillis();
		}

		public void run() {
			while(true) {
				try {
					Thread.sleep(10);
				} catch(InterruptedException e) {
				}
				
				long now = java.lang.System.currentTimeMillis();
				long delta = now-_previous;
				
				_loopistView.onClock( delta );
				
				_previous=now;
			}
		}
	}
	
}